Repository navigation
Train islands-5: timers, child processes and the working directory across the Node island boundary - #3429
Conversation
clearTimeout, clearInterval and clearImmediate did nothing across the boundary, silently in both directions: an island interval the program believed it had stopped kept the process alive, and island code clearing a timer of the program passed Node a number it did not know. The program clears an island timer: a checked-dynamic handle that is not a number goes to the island's own clear function when it is an island object (nodeIsland.clearTimer). The branch is emitted only in a program that can hold island values, which also takes such a handle in TypeScript. Island code clears a program timer: the timer stays the number it is in compiled code. In a program with islands timer and immediate ids start at 2^52, a range Node's own timer ids do not reach, so a number names at most one timer whichever side made it, and the island's clear functions send such a number to the runtime's own clear through the hosted loop's run native. No bridge ABI change. A timer declared by a package that runs in an island stays the island's object, like the other builtin classes without a native object form: the program keeps it in a const, clears it, calls its methods and passes it back. Storing it in a slot typed as a timer is a compile-time refusal by name instead of a TypeError at the crossing. Co-Authored-By: Claude Code <noreply@anthropic.com>
…ams' margins The program-wide fact that a program imports @scriptc/node was cached in a module-level WeakMap; compiler caches live on their owners (compiler-weak-collections.test.ts), so the lowerer keeps it, as it keeps the other facts computed on first use. The new corpus programs ordered timers of the two sides 5 to 20 ms apart, which a loaded sanitized runner can reorder against Node. Every pair of events is now at least 40 ms apart. Co-Authored-By: Claude Code <noreply@anthropic.com>
main removed the lowerer's namesArrayBufferView cache next to the importsNodeApi one this branch adds. Co-Authored-By: Claude Code <noreply@anthropic.com>
…ist up to date scr_node_island_host.h embeds scr_node_island_host.cjs; the comment change in the last commit left it stale (node scripts/node-island-host.mjs). tests/harness/island-conformance/known-failures.json, regenerated: eleven entries had drifted on main through later merges (ten changed their message or became a build refusal, one program matches Node now), none of them through this change. Co-Authored-By: Claude Code <noreply@anthropic.com>
.gitignore: both sides added a diagnostics fixture's node_modules exception at the same place (#3234's node-island-function-value, this branch's node-island-timer-native-use); keep both. Co-Authored-By: Claude Code <noreply@anthropic.com>
Regenerated with SCRIPTC_ISLAND_CONFORMANCE=update on the merged tree. Two entries move, and both move on main itself with nothing applied: npm/hono-zod-default-hook with zod in an island prints its TypeError without brackets, and packages/semver now runs semver in an island by default, so its forced-island entry leaves. The result is entry for entry the list main gives when regenerated; this branch adds none. Co-Authored-By: Claude Code <noreply@anthropic.com>
A ChildProcess that code in a Node island started (a spawn wrapper package) and that reached a native ChildProcess slot threw `expected ChildProcess at $, got Object from a Node island`, and a program that caught it never exited, because the child's stdin was never ended. The slot now holds a child with no state of its own that names the island's object. Every entry point that takes a child, one of its output streams or its input writer hands such a value to the island object's own member: a listener is registered on the island's emitter, a property is read from the island's object, a method is the island's. The island's object is adopted where a dynamic value enters the slot (the checked conversion), and converting the slot's value back answers the island's object. Co-Authored-By: Claude Code <noreply@anthropic.com>
…in a native slot The TypeScript 7 order baselines of the ten corpus programs, the limitations bullet, and the run-time refusals worded by what the listener got. Co-Authored-By: Claude Code <noreply@anthropic.com>
…tion # Conflicts: # docs/content/docs/limitations.mdx # packages/runtime-pack-common/runtime-pack-matrix.mjs # tests/harness/node-island-gc.test.ts
…of its own An object on the link line of every program with Node islands left its file symbol in each of them (48 bytes), also in programs that never use child_process. The SCR_NODE_ISLANDS build of scr_child.c now includes the file, so such a program links nothing of it. Co-Authored-By: Claude Code <noreply@anthropic.com>
docs/content/docs/limitations.mdx: both sides added an include after the event-order partial (this branch's timers, #3271's abort listeners); keep both. lower-node-island.ts: isTimerClearCallee called stripTypeWrappers, which #3254 replaced by the shared withoutParenthesesTypeWrappers. Co-Authored-By: Claude Code <noreply@anthropic.com>
…member table Co-Authored-By: Claude Code <noreply@anthropic.com>
…process Building the code into scr_child.c kept the size of every other island program but changed the order of its symbol table: the unit named the island entry points before the island units did. The unit is its own object again, on the link line through a link feature, islandChild, that a program has when it has both Node islands and child_process. Co-Authored-By: Claude Code <noreply@anthropic.com>
…tions Co-Authored-By: Claude Code <noreply@anthropic.com>
…rogram off a native leak The plateau fixture ends with full collections, so the test reads what is held (two exports after 400 and after 6,000 children) and not what the last scheduled collection left. The general-streams program's stdout listener captured its child, which leaks the child and its stream for a native child on main too; it captures stdin now. Corpus costs for the ten programs. Co-Authored-By: Claude Code <noreply@anthropic.com>
Clear timers across the Node island boundary. Conflicts with main: .gitignore, the lowerer's field and journal lists and the island host script keep both sides; the limitations page takes the include line for the new partial; the generated island host header is regenerated; the island conformance list is main's and is regenerated by its update mode on the train. Co-Authored-By: Claude Code <noreply@anthropic.com>
Hold a child process a Node island made in a native ChildProcess slot. Conflicts with main: the executable feature tables keep both features (processFeatures and islandChild); the limitations page takes the include line for the new partial; the runtime pack matrix and scr_child.c take the branch's additions beside main's. Co-Authored-By: Claude Code <noreply@anthropic.com>
Two clean merges did not compile. The timer clear called the island helper by the name it had before main renamed it (scr_ni_helper). The adopted child process and main each added a nodeIslands member to the dyn emitter's host, in the interface and in the object literal (TS1117): it is one fact, so main's member stays and the copy goes. Co-Authored-By: Claude Code <noreply@anthropic.com>
… removed during the emit A listener of a child process a Node island started that removes a later listener of the same event while the event is being emitted suppressed that listener: off cleared the program's closure at once, and the emit that was under way then called a function with nothing behind it. Node calls every listener that was registered when the emit started. A listener that was removed and registered again during an emit was lost for good. off now marks the registration as removed, so no later off finds it, and leaves the closure with the function: it is released with the function, or when the child closes. tests/corpus/9890-node-island-child-off-during-emit pins six cases over three emits each. The unit also sets SCR_NODE_ISLANDS without testing it: it always reads the island layout of ScrChild and has no block under the define, which is what the runtime pack matrix test asks of a unit that has no such build. Co-Authored-By: Claude Code <noreply@anthropic.com>
Node caches process.cwd() until its own process.chdir runs. A program's process.chdir is a system call Node does not see, so code in an island kept resolving relative paths against the directory the process had left: the conformance lane pinned it (packages/process-cwd, 'false false'). In a program with islands process.chdir goes through the island unit, like the exit code: the runtime's own change, then Node changes to '.', which keeps the directory and drops the cached answer. A Node that is not loaded yet reads the directory when it starts. The other direction already worked: an island's process.chdir is the process's. This is the working-directory half of #3189, on main's island process-state routing; its exit-code half reached main with #3166. tests/corpus/9894-node-island-process-cwd is that PR's program; the conformance entry is removed by the list's update mode. Also records that nodeIsland.clearTimer can throw (island JavaScript runs in it), which the builtin guard asks of every builtin. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Measured warm in a Sandbox: the differential plus the llvm-differential case per flavor. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
HOLD: do not merge yet. The review bot left two findings on this train's child-adoption member ( 🤖 Generated with Claude Code |
… the island's stream Two more places where a child process a Node island started, held in a ChildProcess slot, answered differently from Node. A 'close' listener that takes its arguments as a rest parameter is an untyped listener. The hook that releases the program's listeners when the child closes runs first in that emit and spared only the typed 'close' listeners, so the untyped one was released before its turn and never ran. It is spared too now and releases itself when it has run. tests/corpus/9895-node-island-child-close-rest-listener. pause(), resume(), isPaused() and readableFlowing of the child's stdout and stderr came to main after the adoption was written and had no island arm: they changed the native handle only, the island's stream kept flowing and the flow state read wrongly. They go to the island's stream now, like every other member; of the 55 entry points of the child unit that take a child or one of its streams these four were the ones without the arm. tests/corpus/9896-node-island-child-stream-pause. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Measured warm in a Sandbox: the differential plus the llvm-differential case per flavor. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Runs on this head (f628514; the gate ran on e78aad6, which differs by two cost files under tests/harness), in Sandboxes:
The full round with both native bootstraps is the one in the description (on 90063fc); since then the branch gained two clean merges of main and the two review changes, which touch only the island child unit and the island arm of four stream entries. HOLD stays: do not merge yet. Four macOS jobs of this head are red (failed steps "Node islands (Intel)" and "Tests (darwin suite)") and their logs have not been read yet: they may be this train's new child-process or working-directory programs on macOS. |
|
Hold lifted on head f628514.
🤖 Generated with Claude Code |
Three changes at the Node island boundary, each a place where a program with islands answered differently from Node. They are emitted or linked only in a program that uses an island; the bridge protocol does not change (ABI 20).
What lands
clearTimeout,clearIntervalandclearImmediateof an island's timer in compiled code call Node's own function (it did nothing before, so an island interval kept the process alive). Island code that clears a program timer clears it: in a program with islands the program's timer ids start at 2^52, so a number names one timer whichever side made it. A timer an island package declares stays the island's object; storing it in a variable typed as a timer is a compile-time refusal by name. Immediates take their ids from the timer sequence, which also fixesclearTimeout(immediate)cancelling a timeout with the same number in programs without islands.ChildProcesstype. The slot holds a child with no state of its own that names the island's object: listeners are registered on the island's emitter, properties and methods are the island object's, the streams are the island's streams. Adoption happens at the checked conversion. Limits are on the limitations page ("Child processes from Node islands").process.cwd()until its ownprocess.chdirruns, so after the program changed directory, island code kept resolving relative paths against the old one (conformance entrypackages/process-cwd). In a program with islandsprocess.chdirgoes through the island unit, like the exit code: the runtime's change, then Node changes to".", which drops its cached answer. The exit-code half of Share process.exitCode and the working directory with Node islands #3189 is on main since One registry for process events and one process.exitCode across a Node island #3166.Changes made on the train
scr_node_island_child.c,offduring an emit) was real and is fixed here (c408bae144). A listener that an earlier listener removes while the event is being emitted was not called in that emit; Node calls every listener registered when the emit started. With Hold a child process a Node island made in a native ChildProcess slot #3348 as it was,tests/corpus/9890-node-island-child-off-during-emitprintsa c | a c | a cwhere Node printsa b c | a c | a c, and a listener removed and registered again during an emit was lost for good (a | a | aagainsta b | a b | a b); 20 of 20 runs.offnow marks the registration as removed and leaves the closure with the function.27f0d064e0): the timer clear called the island helper by its old name; the adopted child and main each addednodeIslandsto the dyn emitter's host.nodeIsland.clearTimerandnodeIsland.chdir, which the builtin guard asks for.Validation
Full round on
90063fc94a(this branch before its last merge of main) against main6642a17b5f:npm-static.test.ts"commander compiles statically and byte-matches Node" under a full lane; alone on the same tree it passes in 77.7 s, and the same limit was hit in two unrelated validation rounds today.setImmediatecode: Clear timers across the Node island boundary #3182 removes the separate immediate sequence fromscr_async.c. IR and LLVM are identical on all of them, no function is resized, the size changes by -48 to +24 bytes (50 unchanged, median +8), and no benchmark is among them. This is the exception recorded for Clear timers across the Node island boundary #3182, re-measured.node-island-timers-typescript, a program this train adds: the gate has no libnode pack, so the candidate stops there, as every island program does on both sides. It builds and passes in both lanes.packages/process-cwd, nothing else moves). Island value matrix: 239 of 239, and its update mode changes nothing. The weak-keys fixture passes in both lanes.The head adds a merge of main
59950fefd3with no conflict. On that head: the build, typecheck, guard tests, lint, format and the island tests; the gate's identity and the conformance lane on it are posted as a comment when they finish.Not in this train
#3252 (silent copies), island-native-twins slice 1, #3043, #3124 and the shared globals branch: each needs its owner's restack on current main first.
Supersedes #3182, #3348 and #3189.
🤖 Generated with Claude Code